package com.cys.markdown.doc.admin.shiro;


import com.cys.common.data.DelStatusEnum;
import com.cys.common.data.RespCodeEnum;
import com.cys.common.util.base.StringUtil;
import com.cys.common.util.encrypt.MD5;
import com.cys.mid.core.system.data.LockedEnum;
import com.cys.mid.core.system.interfaces.entity.ISysRight;
import com.cys.mid.core.system.interfaces.entity.ISysUser;
import com.cys.mid.core.system.interfaces.service.SysRightService;
import com.cys.mid.core.system.interfaces.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * SecurityManager-Shiro
 *
 * @author caiyushen
 * @description realm：域，
 * Shiro从从Realm获取安全数据（如用户、角色、权限），就是说SecurityManager要验证用户身份，
 * 那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法；
 * 也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作；
 * 可以把Realm看成DataSource，即安全数据源
 */
public class ShiroRealm extends AuthorizingRealm {

    private Logger logger = LoggerFactory.getLogger(ShiroRealm.class);

    @Resource(name = "sysUserServiceImpl")
    private SysUserService sysUserService;

    @Resource(name = "sysRightServiceImpl")
    private SysRightService sysRightService;

    @Value("${sys.user.password.md5.key}")
    private String sysStaffPasswordMd5Key;

    @Value("${login.fail.count}")
    private Integer loginFailCount;

    /**
     * 为当前登陆成功的用户授予权限和角色，已经登陆成功了
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法(non-Javadoc)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //获取当前登录用户信息
        ISysUser sysUser = (ISysUser) getAvailablePrincipal(principals);
        if (!StringUtil.checkObj(sysUser) || !StringUtil.checkObj(sysUser.getSysUserId())) {
            logger.warn("method[doGetAuthorizationInfo] sysUser is null.");
            return null;
        }
        //根据用户id获取用户权限数据
        List<ISysRight> rightList = sysRightService.findListByUserId(sysUser.getSysUserId());
        if (!StringUtil.checkObj(rightList) || rightList.size() <= 0) {
            logger.warn("method[doGetAuthorizationInfo] rightList is null.");
            return null;
        }
        //记录用户已有的权限列表
        List<String> permissionsList = new ArrayList<>();
        for (ISysRight sysRight : rightList) {
            if (StringUtil.checkObj(sysRight.getSysRightPermission())) {
                permissionsList.add(sysRight.getSysRightPermission());
            }
        }
        authorizationInfo.addStringPermissions(permissionsList);
        return authorizationInfo;
    }

    /**
     * 登录信息和用户验证信息验证，获取认证信息
     *
     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(AuthenticationToken)
     * doGetAuthenticationInfo对于的是对用户验证，主要的一个点在于我们最后返回的那个SimpleAuthenticationInfo
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        ExtAuthenticationToken token = (ExtAuthenticationToken) authenticationToken;//获取token信息

        if (!StringUtil.checkObj(token) || !StringUtil.checkObj(token.getUsername())) {
            logger.warn("method[doGetAuthenticationInfo] account is null.");
            throw new UnknownAccountException(RespCodeEnum.user_account_null.getMsg());
        }
        if (!StringUtil.checkObj(token.getPassword())) {
            logger.warn("method[doGetAuthenticationInfo] passowrd is null.");
            throw new UnknownAccountException(RespCodeEnum.user_password_null.getMsg());
        }

        //根据账号获取用户实体
        ISysUser sysUser = sysUserService.findByAccount(token.getUsername());
        if (!StringUtil.checkObj(sysUser)) {
            logger.warn("method[doGetAuthenticationInfo] sysUser is null. account:" + token.getUsername());
            throw new UnknownAccountException(RespCodeEnum.user_null.getMsg());
        }

        //登录校验
        validate(token, sysUser);

        String password = new String(token.getPassword());

        return new SimpleAuthenticationInfo(sysUser, password, "ShiroRealm");
    }

    /**
     * 业务逻辑校验
     *
     * @param token   前端传过来的用户信息
     * @param sysUser 系统用户信息
     */
    public void validate(ExtAuthenticationToken token, ISysUser sysUser) throws AuthenticationException {
        //校验是否被删除
        isDelValidate(sysUser);
        //判断失败次数
        loginFailCountValidate(sysUser);
        //判断用户是否可用
        isLockedValidate(sysUser);
        //密码校验
        passwordValidate(token, sysUser);

        //登录成功，账号解锁，重置登录失败次数，记录登录ip,登录日期
        sysUser.setIsLocked(LockedEnum.unLocked.getValue());
        sysUser.setLoginFailureCount(0);
        sysUser.setLoginIp(token.getHost());
        sysUser.setLoginDate(new Date());
        sysUserService.updatePart(sysUser);
    }

    /**
     * 校验用户是否被删除
     *
     * @param sysUser 系统用户
     */
    private void isDelValidate(ISysUser sysUser) {
        if (DelStatusEnum.deleted.getValue().equals(sysUser.getDelStatus())) {
            logger.warn(String.format("method[isDelValidate] 账号已删除.id:%s  name:%s", sysUser.getSysUserId(), sysUser.getSysUserName()));
            throw new UnknownAccountException(RespCodeEnum.user_error.getMsg());
        }
    }

    /**
     * 登录失败次数校验
     *
     * @param sysUser 系统用户信息
     */
    private void loginFailCountValidate(ISysUser sysUser) {
        if (loginFailCount <= sysUser.getLoginFailureCount()) {
            logger.warn(String.format("method[isDelValidate] 登陆错误次数超过上限. id:%s  name:%s", sysUser.getSysUserId(), sysUser.getSysUserName()));
            sysUser.setLoginFailureCount(sysUser.getLoginFailureCount() + 1);
            sysUser.setIsLocked(LockedEnum.locked.getValue());
            sysUser.setLockDate(new Date());
            sysUserService.updatePart(sysUser);
            throw new DisabledAccountException(RespCodeEnum.user_locked.getMsg());
        }
    }

    /**
     * 判断用户是否被锁定
     *
     * @param sysUser 系统用户信息
     */
    private void isLockedValidate(ISysUser sysUser) {
        if (LockedEnum.locked.getValue().equals(sysUser.getIsLocked())) {
            logger.warn(String.format("method[isDelValidate] 账号已被锁定. id:%s  name:%s", sysUser.getSysUserId(), sysUser.getSysUserName()));
            throw new LockedAccountException(RespCodeEnum.user_locked.getMsg());
        }
    }


    /**
     * 校验用户密码是否正确
     *
     * @param token   前端传过来的用户信息
     * @param sysUser 系统用户信息
     */
    private void passwordValidate(ExtAuthenticationToken token, ISysUser sysUser) throws AuthenticationException {
        //前端密码加密后与 数据库密码 进行匹配校验
        if (!sysUser.getSysUserPassword().equals(MD5.encode(new String(token.getPassword()) + sysStaffPasswordMd5Key))) {
            logger.warn("method[passwordValidate] 账号/密码错误. account:" + token.getUsername());
            //允许错误的次数 减去 已经登录失败的次数
            int count = loginFailCount - (sysUser.getLoginFailureCount() + 1) > 0 ? loginFailCount - (sysUser.getLoginFailureCount() + 1) : 0;
            //记录密码错误次数
            sysUser.setLoginFailureCount(sysUser.getLoginFailureCount() + 1);
            sysUserService.updatePart(sysUser);
            throw new IncorrectCredentialsException("账号密码错误:还有" + count + "次机会");
        }
    }


}